package com.otaliastudios.cameraview.frame

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import com.otaliastudios.cameraview.LogPlus
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import java.util.concurrent.LinkedBlockingQueue

class MjpegInputStream(`in`: InputStream?) :
    DataInputStream(BufferedInputStream(`in`, FRAME_MAX_LENGTH)) {
    /**
     * 用打印数据发现 每一个jpg格式的图片 开始两字节都是 0xFF,0xD8
     */
    private val SOI_MARKER = byteArrayOf(0xFF.toByte(), 0xD8.toByte())
    private val EOF_MARKER = byteArrayOf(0xFF.toByte(), 0xD9.toByte())

    /**
     * 表示服务器发给客户端的一帧数据的长度
     */
    private val CONTENT_LENGTH = "Content-Length"
    private var mContentLength = -1
    var header: ByteArray? = null
    var frameData: ByteArray? = null
    var headerLen = -1
    var headerLenPrev = -1
    val frameQueue = LinkedBlockingQueue<ByteArray>(20)


    companion object {
        private const val HEADER_MAX_LENGTH = 100

        //private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
        private const val FRAME_MAX_LENGTH = 200000
        private const val TAG = "MJPEG"
        private const val DEBUG = false
        fun read(surl: String?): MjpegInputStream? {
            try {
                val url = URL(surl)
                val urlConnection = url.openConnection() as HttpURLConnection
                return MjpegInputStream(urlConnection.inputStream)
            } catch (e: Exception) {
            }
            return null
        }
    }

    //在数据流里面找SOI_MARKER={(byte)0xFF,(byte) 0xD8}
    @Throws(IOException::class)
    private fun getEndOfSeqeunce(`in`: DataInputStream, sequence: ByteArray): Int {
        var seqIndex = 0
        var c: Byte
        for (i in 0 until FRAME_MAX_LENGTH) {
            c = `in`.readUnsignedByte().toByte()
            if (c == sequence[seqIndex]) {
                seqIndex++
                if (seqIndex == sequence.size) {
                    return i + 1
                }
            } else seqIndex = 0
        }
        return -1
    }

    /**
     *  * 此方法功能是找到索引0xFF,0XD8在字符流的位置
     * 整个数据流形式：http头信息 帧头(0xFF 0xD8) 帧数据 帧尾(0xFF 0xD9)
     * 1、首先通过0xFF 0xD8找到帧头位置
     * 2、帧头位置前的数据就是http头，里面包含Content-Length，这个字段指示了整个帧数据的长度
     * 3、帧头位置后面的数据就是帧图像的开始位置
     */
    @Throws(IOException::class)
    private fun getStartOfSequence(`in`: DataInputStream, sequence: ByteArray): Int {
        val end = getEndOfSeqeunce(`in`, sequence)
        return if (end < 0) -1 else end - sequence.size
    }

    @Throws(IOException::class)
    private fun getEndOfSeqeunceSimplified(`in`: DataInputStream, sequence: ByteArray): Int {
        val startPos = mContentLength / 2
        val endPos = 3 * mContentLength / 2
        skipBytes(headerLen + startPos)
        var seqIndex = 0
        var c: Byte
        for (i in 0 until endPos - startPos) {
            c = `in`.readUnsignedByte().toByte()
            if (c == sequence[seqIndex]) {
                seqIndex++
                if (seqIndex == sequence.size) {
                    return headerLen + startPos + i + 1
                }
            } else seqIndex = 0
        }
        return -1
    }

    //从http的头信息中获取Content-Length，知道一帧数据的长度
    @Throws(IOException::class, NumberFormatException::class, IllegalArgumentException::class)
    private fun parseContentLength(headerBytes: ByteArray): Int {
        /**
         * 根据字节流创建ByteArrayInputStream流
         * Properties是java.util包里的一个类，它有带参数和不带参数的构造方法，表示创建无默认值和有默认值的属性列表
         * 根据流中的http头信息生成属性文件，然后找到属性文件CONTENT_LENGTH的value，这就找到了要获得的帧数据大小
         * 创建一个 ByteArrayInputStream，使用 headerBytes作为其缓冲区数组
         */
        val headerIn = ByteArrayInputStream(headerBytes)
        val props = Properties()/*创建一个无默认值的空属性列表*/
        props.load(headerIn)/*从输入流中生成属性列表（键和元素对）。*/
        return props.getProperty(CONTENT_LENGTH).toInt()//这个位置如果错误记得看一下，注意大小写对比
    }

    @Throws(IOException::class)
    fun readMjpegFrame(): Bitmap? {
        mark(FRAME_MAX_LENGTH)
        val headerLen: Int = try {
            getStartOfSequence(this, SOI_MARKER)
        } catch (e: IOException) {
            if (DEBUG) Log.d(TAG, "IOException in betting headerLen.")
            reset()
            return null
        }
        reset()
        if (header == null || headerLen != headerLenPrev) {
            header = ByteArray(headerLen)
            if (DEBUG) Log.d(TAG, "header renewed $headerLenPrev -> $headerLen")
        }
        headerLenPrev = headerLen
        readFully(header)
        var ContentLengthNew = -1
        try {
            ContentLengthNew = parseContentLength(header!!)
        } catch (nfe: NumberFormatException) {
            ContentLengthNew = getEndOfSeqeunceSimplified(this, EOF_MARKER)
            if (ContentLengthNew < 0) {
                if (DEBUG) Log.d(TAG, "Worst case for finding EOF_MARKER")
                reset()
                ContentLengthNew = getEndOfSeqeunce(this, EOF_MARKER)
            }
        } catch (e: IllegalArgumentException) {
            if (DEBUG) Log.d(TAG, "IllegalArgumentException in parseContentLength")
            ContentLengthNew = getEndOfSeqeunceSimplified(this, EOF_MARKER)
            if (ContentLengthNew < 0) {
                if (DEBUG) Log.d(TAG, "Worst case for finding EOF_MARKER")
                reset()
                ContentLengthNew = getEndOfSeqeunce(this, EOF_MARKER)
            }
        } catch (e: IOException) {
            if (DEBUG) Log.d(TAG, "IOException in parseContentLength")
            reset()
            return null
        }
        mContentLength = ContentLengthNew
        reset()
        if (frameData == null) {
            frameData = ByteArray(FRAME_MAX_LENGTH)
            if (DEBUG) Log.d(TAG, "frameData newed cl=$FRAME_MAX_LENGTH")
        }
        if (mContentLength + HEADER_MAX_LENGTH > FRAME_MAX_LENGTH) {
            frameData = ByteArray(mContentLength + HEADER_MAX_LENGTH)
            if (DEBUG) Log.d(TAG, "frameData renewed cl=" + (mContentLength + HEADER_MAX_LENGTH))
        }
        skipBytes(headerLen)
        readFully(frameData, 0, mContentLength)
        return BitmapFactory.decodeByteArray(frameData, 0, mContentLength)

    }


    @Throws(IOException::class)
    fun readFrame(): Int {
        mark(FRAME_MAX_LENGTH)/*流中当前的标记位置*/
        val headerLen: Int = try {
            getStartOfSequence(this, SOI_MARKER)
        } catch (e: IOException) {
            LogPlus.e(TAG, "IOException in betting headerLen.")
            reset()/*将缓冲区的位置重置为标记位置*/
            return -1
        }
        reset()
        if (header == null || headerLen != headerLenPrev) {
            header = ByteArray(headerLen)
            if (DEBUG) Log.d(TAG, "header renewed $headerLenPrev -> $headerLen")
        }
        headerLenPrev = headerLen
        readFully(header) /*会一直阻塞等待，直到数据全部到达(数据缓冲区装满)*/
        var ContentLengthNew = -1
        try {
            ContentLengthNew = parseContentLength(header!!)
        } catch (nfe: NumberFormatException) {
            ContentLengthNew = getEndOfSeqeunceSimplified(this, EOF_MARKER)
            if (ContentLengthNew < 0) {
                if (DEBUG) Log.d(TAG, "Worst case for finding EOF_MARKER")
                reset()
                ContentLengthNew = getEndOfSeqeunce(this, EOF_MARKER)
            }
        } catch (e: IllegalArgumentException) {
            if (DEBUG) Log.d(TAG, "IllegalArgumentException in parseContentLength")
            ContentLengthNew = getEndOfSeqeunceSimplified(this, EOF_MARKER)
            if (ContentLengthNew < 0) {
                if (DEBUG) Log.d(TAG, "Worst case for finding EOF_MARKER")
                reset()
                ContentLengthNew = getEndOfSeqeunce(this, EOF_MARKER)
            }
        } catch (e: IOException) {
            if (DEBUG) Log.d(TAG, "IOException in parseContentLength")
            reset()
            return -1
        }
        mContentLength = ContentLengthNew
        reset()
        if (frameData == null) {
            frameData = ByteArray(FRAME_MAX_LENGTH) //根据帧数据的大小创建字节数组
            if (DEBUG) Log.d(TAG, "frameData newed cl=$FRAME_MAX_LENGTH")
        }
        if (mContentLength + HEADER_MAX_LENGTH > FRAME_MAX_LENGTH) {
            frameData = ByteArray(mContentLength + HEADER_MAX_LENGTH)
            if (DEBUG) Log.d(TAG, "frameData renewed cl=" + (mContentLength + HEADER_MAX_LENGTH))
        }
        skipBytes(headerLen)
        readFully(frameData, 0, mContentLength)
        val newFrame = ByteArray(mContentLength) //根据帧数据的大小创建字节数组
        System.arraycopy(frameData, 0, newFrame, 0, mContentLength)
        frameQueue.offer(newFrame)

        return 0
    }

    fun freeCameraMemory() {
        close()
    }
}